package com.febs.security.config;

import com.febs.common.domain.FebsConstant;
import com.febs.common.helper.StringRedisHelper;
import com.febs.security.code.img.ImageCodeFilter;
import com.febs.security.code.sms.DefaultSmsSender;
import com.febs.security.code.sms.SmsCodeFilter;
import com.febs.security.code.sms.SmsCodeSender;
import com.febs.security.filter.FebsTokenFilter;
import com.febs.security.handler.FebsAuthenticationFailureHandler;
import com.febs.security.handler.FebsAuthenticationSuccessHandler;
import com.febs.security.handler.FebsLogoutHandler;
import com.febs.security.properties.FebsSecurityProperties;
import com.febs.security.service.FebsUserDetailServiceImpl;
import com.febs.security.session.FebsInvalidSessionStrategy;
import com.febs.security.xss.XssFilter;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.social.security.SpringSocialConfigurer;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * security 配置中心
 *
 * @author zhongyj <1126834403@qq.com><br/>
 * @date 2021/6/18
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class FebsSecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${" + FebsSecurityProperties.ENABLE_REDIS_CACHE + "}")
    private boolean enableRedisCache;

    private StringRedisHelper redisHelper;

    @Resource
    private FebsAuthenticationSuccessHandler febsAuthenticationSuccessHandler;
    @Resource
    private FebsAuthenticationFailureHandler febsAuthenticationFailureHandler;
    @Resource
    private FebsSecurityProperties febsSecurityProperties;
    @Resource
    private FebsSmsCodeAuthenticationSecurityConfig febsSmsCodeAuthenticationSecurityConfig;
    @Resource
    private FebsUserDetailServiceImpl febsUserDetailServiceImpl;
    @Resource
    private DataSource dataSource;
    @Resource
    private SpringSocialConfigurer febsSocialSecurityConfig;
    @Resource
    private AccessDeniedHandler accessDeniedHandler;
    @Resource
    private FebsTokenFilter tokenFilter;

    @Autowired(required = false)
    public void setRedisHelper(StringRedisHelper redisHelper) {
        this.redisHelper = redisHelper;
    }

    /**
     * spring security自带的密码加密工具类
     *
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SessionStrategy sessionStrategy() {
        return new HttpSessionSessionStrategy();
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    /**
     * 处理 rememberMe 自动登录认证
     *
     * @return PersistentTokenRepository
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        jdbcTokenRepository.setCreateTableOnStartup(false);
        return jdbcTokenRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        String[] anonResourcesUrl = StringUtils.splitByWholeSeparatorPreserveAllTokens(febsSecurityProperties.getAnonResourcesUrl(), ",");

        ImageCodeFilter imageCodeFilter = new ImageCodeFilter();
        imageCodeFilter.setAuthenticationFailureHandler(febsAuthenticationFailureHandler);
        imageCodeFilter.setSecurityProperties(febsSecurityProperties);
        imageCodeFilter.setEnableRedisCache(enableRedisCache);
        imageCodeFilter.setRedisHelper(redisHelper);
        imageCodeFilter.setSessionStrategy(sessionStrategy());
        imageCodeFilter.afterPropertiesSet();

        SmsCodeFilter smsCodeFilter = new SmsCodeFilter();
        smsCodeFilter.setAuthenticationFailureHandler(febsAuthenticationFailureHandler);
        smsCodeFilter.setSecurityProperties(febsSecurityProperties);
        smsCodeFilter.setSessionRegistry(sessionRegistry());
        smsCodeFilter.afterPropertiesSet();

        // 权限不足处理器
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
        http
                // 短信验证码校验
                .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)
                // 添加图形证码校验过滤器
                .addFilterBefore(imageCodeFilter, UsernamePasswordAuthenticationFilter.class)
                // 添加过滤器将 token 解析，将用户所有的权限写入本次 Spring Security 的会话
                .addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 表单方式
        http.formLogin()
                .loginPage(febsSecurityProperties.getLoginUrl()) // 未认证跳转 URL
                .loginProcessingUrl(febsSecurityProperties.getCode().getImage().getLoginProcessingUrl()) // 处理登录认证 URL
                .successHandler(febsAuthenticationSuccessHandler) // 处理登录成功
                .failureHandler(febsAuthenticationFailureHandler); // 处理登录失败
        // 添加记住我功能
        http.rememberMe()
                .tokenRepository(persistentTokenRepository()) // 配置 token 持久化仓库
                .tokenValiditySeconds(febsSecurityProperties.getRememberMeTimeout()) // rememberMe 过期时间，单为秒
                .userDetailsService(febsUserDetailServiceImpl); // 处理自动登录逻辑

//                .sessionManagement() // 配置 session管理器
//                .invalidSessionStrategy(invalidSessionStrategy()) // 处理 session失效
//                .maximumSessions(febsSecurityProperties.getSession().getMaximumSessions()) // 最大并发登录数量
//                .expiredSessionStrategy(new FebsExpiredSessionStrategy()) // 处理并发登录被踢出
//                .sessionRegistry(sessionRegistry()) // 配置 session注册中心
//                .and()
//                .and()
        // 配置登出
        http.logout()
                .addLogoutHandler(logoutHandler()) // 配置登出处理器
                .logoutUrl(febsSecurityProperties.getLogoutUrl()) // 处理登出 url
                .logoutSuccessUrl("/") // 登出后跳转到 /
                .deleteCookies("JSESSIONID"); // 删除 JSESSIONID
        // 授权配置
        http.authorizeRequests()
                .antMatchers(anonResourcesUrl).permitAll() // 免认证静态资源路径
                .antMatchers(
                        febsSecurityProperties.getLoginUrl(), // 登录路径
                        FebsConstant.FEBS_REGIST_URL, // 用户注册 url
                        febsSecurityProperties.getIndexUrl(),
                        febsSecurityProperties.getCode().getImage().getCreateUrl(), // 创建图片验证码路径
                        febsSecurityProperties.getCode().getSms().getCreateUrl(), // 创建短信验证码路径
                        febsSecurityProperties.getSocial().getSocialRedirectUrl(), // 重定向到社交账号注册（绑定）页面路径
                        febsSecurityProperties.getSocial().getSocialBindUrl(), // 社交账号绑定 URL
                        febsSecurityProperties.getSocial().getSocialRegistUrl() // 注册并绑定社交账号 URL
                ).permitAll() // 配置免认证路径
                .anyRequest()  // 所有请求
                .authenticated() // 都需要认证
                .and()
                .csrf().disable()
                .apply(febsSmsCodeAuthenticationSecurityConfig) // 添加短信验证码认证流程
                .and()
                .apply(febsSocialSecurityConfig); // social 配置
        // session 配置
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 调整为让 Spring Security 不创建和使用 session
    }

    @Bean
    @ConditionalOnMissingBean(SmsCodeSender.class)
    public SmsCodeSender smsCodeSender() {
        return new DefaultSmsSender();
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    /**
     * 配置登出处理器
     *
     * @return LogoutHandler
     */
    @Bean
    public LogoutHandler logoutHandler() {
        FebsLogoutHandler febsLogoutHandler = new FebsLogoutHandler();
        febsLogoutHandler.setSessionRegistry(sessionRegistry());
        return febsLogoutHandler;
    }

    @Bean
    public InvalidSessionStrategy invalidSessionStrategy() {
        FebsInvalidSessionStrategy febsInvalidSessionStrategy = new FebsInvalidSessionStrategy();
        febsInvalidSessionStrategy.setSecurityProperties(febsSecurityProperties);
        return febsInvalidSessionStrategy;
    }

    /**
     * XssFilter Bean
     */
//    @Bean
    @SuppressWarnings({"unchecked", "rawtypes"})
    public FilterRegistrationBean xssFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new XssFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.setEnabled(true);
        filterRegistrationBean.addUrlPatterns("/*");
        Map<String, String> initParameters = new HashMap<>(4);
        initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
        initParameters.put("isIncludeRichText", "true");
        filterRegistrationBean.setInitParameters(initParameters);
        return filterRegistrationBean;
    }

}
